package fri.pipt.agent.indy;

import java.util.Arrays;
import java.util.HashMap;

import fri.pipt.agent.Agent;
import fri.pipt.agent.Membership;
import fri.pipt.agent.deathrow.VisualDebugger;
import fri.pipt.arena.TerminalView;
import fri.pipt.protocol.Neighborhood;
import fri.pipt.protocol.Position;
import fri.pipt.protocol.Message.Direction;

@Membership("samples")
public class Indy extends Agent {

	public int getCenterX()
	{
		return centerX;
	}

	public void setCenterX(int centerX)
	{
		this.centerX = centerX;
	}

	public int getCenterY()
	{
		return centerY;
	}

	public void setCenterY(int centerY)
	{
		this.centerY = centerY;
	}

	public int getMinX()
	{
		return minX;
	}

	public void setMinX(int minX)
	{
		this.minX = minX;
	}

	public int getMaxX()
	{
		return maxX;
	}

	public void setMaxX(int maxX)
	{
		this.maxX = maxX;
	}

	public int getMinY()
	{
		return minY;
	}

	public void setMinY(int minY)
	{
		this.minY = minY;
	}

	public int getMaxY()
	{
		return maxY;
	}

	public void setMaxY(int maxY)
	{
		this.maxY = maxY;
	}

	private static enum AgentState {
		EXPLORE, SEEK, RETURN
	}

	private TerminalView arena = new TerminalView();

	public int getX()
	{
		return x;
	}

	public void setX(int x)
	{
		this.x = x;
	}

	public int getY()
	{
		return y;
	}

	public void setY(int y)
	{
		this.y = y;
	}

	private int x = 0;

	private int y = 0;
	
	/*
	 * Map je int tabela 1000x1000
	 * 
	 * int je sestavljen:
	 * 0 - 7: nasi agenti
	 * 8 - 19: agenti drugih ekip
	 * 20: nasa baza
	 * 21: nasa zastava
	 * 22: nas agent
	 * 23: prazno mesto
	 * 
	 * negativna stevila: zid, baza druge ekipe, zastava druge ekipe
	 * 
	 * vrednost 0: še neraziskano polje
	 * 
	 */
	public int[][] Map;
	
	private int centerX;
	
	private int centerY;
	
	private int minX = 1000, maxX = 0, minY = 1000, maxY = 0;
	
	private HashMap<String, Position> registry = new HashMap<String, Position>();

	private AgentState state = AgentState.EXPLORE;

	private Decision left, right, up, down, still;
	
	private Decision[] decisions;
	
	private int sn = 1;
	private long sx, sy;
	
	
	@Override
	public void initialize() {

		Map = new int[1000][1000];
		centerX = 499;
		centerY = 499;
		
		left = new Decision(0, Direction.LEFT);
		right = new Decision(0, Direction.RIGHT);
		up = new Decision(0, Direction.UP);
		down = new Decision(0, Direction.DOWN);
		still = new Decision(0, Direction.NONE);
		
		decisions = new Decision[] {
			left, right, up, down, still	
		};
		System.out.println("**************************************");
		System.out.println("**************INIT AGENT**************");
	//	vd = new VisualDebugger(this);
		System.out.println("**************************************");
	}

	VisualDebugger vd;
	
	@Override
	public void receive(int from, byte[] message) {

		String msg = new String(message);

		System.out.format("Message recived from %d: %s", from, msg);

	}

	@Override
	public void state(int stamp, Neighborhood neighborhood, Direction direction, boolean hasFlag) {

		this.neighborhood = neighborhood;
		this.direction = direction;

		if (state != AgentState.RETURN && hasFlag)
			state = AgentState.RETURN;
	//	vd.renderArena();

	/*	synchronized (waitMutex) {
			waitMutex.notify();
		}*/
	}

	@Override
	public void terminate() 
	{

	}

	private Direction moving = Direction.NONE;

	protected static class Decision implements Comparable<Decision> {

		private float weight;

		private Direction direction;

		public float getWeight() {
			return weight;
		}

		public void setWeight(float weight) {
			this.weight = weight;
		}
		
		public void multiplyWeight(float f) {
			this.weight *= f;
		}
		
		public Direction getDirection() {
			return direction;
		}

		public void setDirection(Direction direction) {
			this.direction = direction;
		}

		public Decision(float weight, Direction direction) {
			super();
			this.weight = weight;
			this.direction = direction;
		}

		@Override
		public int compareTo(Decision o) {
			if (weight < o.weight)
				return -1;

			if (weight > o.weight)
				return 1;

			return 0;
		}

		public String toString() {
			return String.format("%s (%.2f)", direction.toString(), weight);
		}
		
	}

	@Override
	public void run() {

		while (isAlive()) {
			
			try {

				scanAndWait();

				System.out.printf("Current position: %d, %d, state: %s \n", x, y, state.toString());

				if (direction == Direction.NONE) {

					if (moving != Direction.NONE) {
						switch (moving) {
						case DOWN:
							y += 1;
							break;
						case UP:
							y -= 1;
							break;
						case LEFT:
							x -= 1;
							break;
						case RIGHT:
							x += 1;
							break;
						}

						sn++;
						sx += x;
						sy += y;
						
					}
					
					analyzeNeighborhood(neighborhood);
					
					Decision d = updateDecisions(neighborhood, state);
					
					System.out.printf("Best move: %s %.2f \n", d.getDirection().toString(), d.getWeight());
					
					if (d.getDirection() != Direction.NONE) 
						move(d.getDirection());

					moving = d.getDirection();
					
				}

			} catch (InterruptedException e) {
				e.printStackTrace();
			}

			try {
				System.out.println("Thread sleep: " + getSpeed());
				Thread.sleep(getSpeed()/1000 );
			} catch (InterruptedException e) { 
			}

		}
		System.out.println("**************************************");
		System.out.println("**************DIED********************");
		System.out.println("**************************************");
		vd.setVisible(false);
		vd.dispose();
		vd = null;
	}

//	private Object waitMutex = new Object();

	private Neighborhood neighborhood;

	private Direction direction;

	private void scanAndWait() throws InterruptedException {

		//synchronized (waitMutex) {
			scan(0);
//			waitMutex.wait();
	//	}

	}

	private void analyzeNeighborhood(Neighborhood n) {

		for (int j = -n.getSize(); j <= n.getSize(); j++) {

			for (int i = -n.getSize(); i <= n.getSize(); i++) {

				Map[centerY + y + j][centerX + x + i] = 0;
				
				if(centerX + x + i < minX){
					minX = centerX + x + i;
				}
				
				if(centerY + y + j < minY){
					minY = centerY + y + j;
				}
				
				if(centerX + x + i > maxX){
					maxX = centerX + x + i;
				}
				
				if(centerY + y + j > maxY){
					maxY = centerY + y + j;
				}
				//**
				if (n.getCell(i, j) == Neighborhood.EMPTY) {
					
					Map[centerY + y + j][centerX + x + i] = 1 << 23; 

					continue;
				}
				//**
				if (n.getCell(i, j) == Neighborhood.FLAG) {

					System.out.println("Found flag !!!");
					
					state = AgentState.SEEK;

					Map[centerY + y + j][centerX + x + i] = 1 << 21; 

					continue;
				}

				//**
				if (n.getCell(i, j) == Neighborhood.HEADQUARTERS) {

					Map[centerY + y + j][centerX + x + i] = 1 << 20; 

					continue;
				}
				//**
				if (n.getCell(i, j) == Neighborhood.WALL) {
					
					Map[centerY + y + j][centerX + x + i] = -1; 

					continue;
				}
				//**
				if (n.getCell(i, j) > 0) {

					if (i != 0 && j != 0) {
						
						send(n.getCell(i, j), "Hello " + n.getCell(i, j) + "!");
						
						Map[centerY + y + j][centerX + x + i] = n.getCell(i, j) << 8;
					
					} else {
						
						Map[centerY + y + j][centerX + x + i] = 1 << 22;
						
					}

					continue;
				}
				
			}

		}
		printMap();
	}

	private void printMap() {

		/*int polje;
		for(int j = minY; j <= maxY; j++) {
			
			for(int i = minX; i <= maxX; i++) {
				
				polje = Map[j][i];
				
				if(1 << 23 == polje) {
					
					System.out.print(" ");
					
					continue;
				}
				
				if(polje == 0) {
					
					System.out.print("0");
					
					continue;
				}
				
				if(polje < 0) {
					
					System.out.print("?");
					
					continue;
				}
				
				if(1 << 20 == polje) {
					
					System.out.print("b");
					
					continue;
				}
				
				if(1 << 21 == polje) {
					
					System.out.print("f");
					
					continue;
				}
				
				if(1 << 22 == polje) {
					
					System.out.print("I");
					
					continue;
				}
				
				int AND = 0xfff & polje;
				
				if(AND > 0) {
					
					System.out.print("a");
					
					continue;
				}
			}
			
			System.out.println();
		}*/
	}
	
	private Decision updateDecisions(Neighborhood n, AgentState state) {
		
		still.setWeight(0.01f);
		down.setWeight(canMove(n, 0, 1, state) ? 1 : 0);
		up.setWeight(canMove(n, 0, -1, state) ? 1 : 0);
		left.setWeight(canMove(n, -1, 0, state) ? 1 : 0);
		right.setWeight(canMove(n, 1, 0, state) ? 1 : 0);
		
		switch (state) {
		case EXPLORE:
			
			int cx = (int) (sx / sn);
			int cy = (int) (sy / sn);
			
			//System.out.printf("%d %d %d %d %d\n", sx, sy, sn, cx, cy);
			
			//System.out.printf("%.2f\n", Math.log(Math.max(2, cy - y)));
			
			down.multiplyWeight((float)Math.log(Math.max(2, cy - y)) * random(0.7f, 1));
			up.multiplyWeight((float)Math.log(Math.max(2, y - cy)) * random(0.7f, 1));
			left.multiplyWeight((float)Math.log(Math.max(2, x - cx)) * random(0.7f, 1));
			right.multiplyWeight((float)Math.log(Math.max(2, cx - x)) * random(0.7f, 1));
			
			break;
		case RETURN: {
			
			Position p = registry.get("hq");
			
			if (p == null)
				return updateDecisions(n, AgentState.EXPLORE);
			
			down.multiplyWeight(Math.max(0.2f, p.getY() - y));
			up.multiplyWeight(Math.max(0.2f, y - p.getY()));
			left.multiplyWeight(Math.max(0.2f, x - p.getX()));
			right.multiplyWeight(Math.max(0.2f, p.getX() - x));
			
			break;
		}
		case SEEK: {
			
			Position p = registry.get("flag");
			
			if (p == null)
				return updateDecisions(n, AgentState.EXPLORE);
			
			down.multiplyWeight(Math.max(0.2f, p.getY() - y));
			up.multiplyWeight(Math.max(0.2f, y - p.getY()));
			left.multiplyWeight(Math.max(0.2f, x - p.getX()));
			right.multiplyWeight(Math.max(0.2f, p.getX() - x));

			break;
		}
		}
		
		Arrays.sort(decisions);
		
		/*for (Decision d : decisions) 
			System.out.println(d);*/
		
		return decisions[decisions.length - 1];
		
	}
	
	private boolean canMove(Neighborhood n, int x, int y, AgentState state) {
		
		switch (state) {
		case RETURN:
			return n.getCell(x, y) == Neighborhood.EMPTY || n.getCell(x, y) == Neighborhood.HEADQUARTERS;
		case SEEK:
			return n.getCell(x, y) == Neighborhood.EMPTY || n.getCell(x, y) == Neighborhood.FLAG;
		default:
			return n.getCell(x, y) == Neighborhood.EMPTY;		
		}
		
	}
	
	private static float random(float min, float max) {
		
		return (float) (Math.random() * (max - min)) + min;
		
	}
}
